Skip to content

[Local|Release] Release desktop 2.2.2 - #2293

Merged
MODSetter merged 233 commits into
mainfrom
dev
Oct 16, 2026
Merged

MODSetter merged 233 commits into
mainfrom
dev

Conversation

@MODSetter

Copy link
Copy Markdown
Owner

Desktop release 2.2.2: dev into main. The tag v2.2.2 goes on this pull request's merge commit (surfsense_local/RELEASE.md).

What's in it since 2.2.1 (the full list is in the v2.2.2 changelog entry, #2292):

Before merging:

  • The README format table was re-checked against dev: Recordings added; the other rows still hold.
  • Read every Security Scan finding. It flags pinned sha256 hashes and test fixtures (Security Scan: stop flagging pinned hashes and test fixtures on release PRs #2225).
  • Build audio.cpp fails on PRs since 15 Oct (Windows: the Visual Studio 18 generator; Linux: reading the recogniser's pin). That is not this release's code, but the tag's Desktop Release builds audio.cpp too, so watch it.

After merging:

  • Tag v2.2.2, draft release, then the Known issues from docs(changelog): v2.2.2 #2292 above the generated notes.
  • Redeploy from main on Coolify: the license server (APP_RELEASE_VERSION is compiled in, so a redeploy, not a restart), the plugin host and the plugin registry. The website deploys itself on Vercel.

AnishSarkar22 and others added 30 commits October 4, 2026 18:25
Swap the old "open web research" OG image for one cropped from the homepage banner, saved as a real 1200x630 PNG. The old file was a JPEG at 1376x768, though the metadata declared 1200x630.

Drop og-image-twitter.png and point twitter:image at og-image.png. The two files were identical.

Add openGraph.images to the home, announcements, privacy, terms and
docs pages. Next merges metadata shallowly, so a page that sets its own openGraph without images loses the layout's image.
Once the first token has gone out the status is already 200, so llama.cpp
and OpenRouter report a later failure as a `data:` chunk carrying `error`.
`_delta()` read that chunk as an empty delta, so the stream closed as if the
model had finished: chat stored the cut-off text as a completed reply with
no error, and Studio handed it to the JSON parser.

Raise `StreamedError` with the endpoint's message instead, so the turn
takes the existing mid-reply failure path: an `error` frame, partial text
kept.
audio.cpp's joined WAV is encoded in-process by LAME through lameenc, at
64 kbit/s per channel, the model's channels and rate. Without lameenc the
episode stays WAV. The frozen worker ships lameenc's dist-info for LAME's
LGPL text.

Fixes #2001
Collect licence notices for the npm trees, the Python sidecars, the staged
native runtimes and the bundled model packs, merge them into
THIRD_PARTY_NOTICES.json and .txt, and ship them as resources/notices.
The merge fails the build when a dependency has no licence text and the
reviewed allowlist gives no reason.

Refs #1941
Drop the WebGL dither shader behind the hero headline, along with its
styles and the unused CTA dither image. The hero is now a plain server
component, leaving the claims tabs and format spotlight as the only
client modules on the page.
Turn the full-width nav into a rounded bar inset from the viewport edges
and pinned to the top, with a Download button. Phones and tablets get a
menu button that opens a separate floating card over a dimmed, blurred
page. The announcement banner now scrolls away instead of sticking with
the nav.
Replace the ss-home-* classes on every marketing page with Tailwind
default utilities. Shared text styles live in siteText, and the FAQ
markup repeated across eight pages is now one SiteFaqItem component.

home.css shrinks from ~1,840 to ~530 lines. It keeps the marketing
palette scoped to .ss-home, so the app's colours stay separate, plus
the rules that need real CSS: grid hairlines, tables, menu and FAQ
motion, the logo cycle and reduced motion. Those rules sit in the
components layer, so utilities override them.

Headings now step at breakpoints instead of scaling fluidly.
Swap the cross-fading 4x2 logo grid for a single row that scrolls on a
loop, under a pill label on a faded rule. The row fades out at both
edges, pauses on hover, holds still for reduced motion, and spaces
logos closer on phones.

Also thicken the nav surface's rim from 3px to 5px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pin the canonical light palette inside .ss-home, so marketing pages
are light for every visitor while the app stays forced dark. The dark:
variant no longer applies inside .ss-home, where it leaked the app's
dark styles.

Follow-ups for a light ground:
- drop prose-invert on blog, changelog, privacy and terms
- invert only the light-drawn logos; give the illustrations a shadow
- keep the nav icon dark on marketing pages, white elsewhere
- soften the nav shadow and draw the spotlight glow from the palette

Also narrow the nav to 64rem, drop the Resources panel a little lower,
and remove the announcement bar's side rules and the rules around the
logo marquee.
The banner above the navbar on every page is gone. The announcement is now
a pill above the homepage headline that links to /sunset. It has no dismiss
button, so it no longer needs localStorage.

NEXT_PUBLIC_TOP_ANNOUNCEMENT_ENABLED keeps its name, so existing deployments
keep working.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run the hero full-bleed behind the floating nav over a dithered moonlit scene, and close the page with a matching CTA card on the same image.
The image ships as a 3104px WebP master through next/image: AVIF at each screen's width, a priority load for the hero and an inlined blur preview. The CTA reuses the cached file.

- add HomeScene, HomeCta and HomeDownloadButton
- enable AVIF and allow quality 85 in next.config, since 75 smudges
  the dither
- restyle the announcement pill to read on the photo
- Raise the nav bar to 56px, match its right inset to the logo's, and
  give it a translucent cool-grey rim that reads on the light page
- Add a progressive blur under the bar, stacked in four layers so
  content softens toward the top edge
- Swap the hand-drawn burger for Hugeicons' two-line menu icon, morphing
  into a cross
- Fade the hero scene into the page just below the fold, and pull the
  hero up by the taller nav's height
- Give HomeScene a real `sizes` per use, so phones fetch an image wide
  enough for the cover crop instead of a blurry 100vw one

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the three illustrated bento cells under "Runs entirely on your
machine" with one isometric diorama of the local stack. On large
screens the section pins for a screen and each claim (index, model,
outputs) lights its objects in blue as you scroll through it; below lg
it flows normally and a claim activates as it crosses the viewport.

The diorama is generated SVG (generate-air-gap-diorama.mjs writes
air-gap-diorama-svg.ts), so it ships with no image request; its active
step crossfades via data attributes in home.css.

Also add HomeBadge, a pill tuned to the marketing ground, and use it in
place of shadcn's secondary Badge on announcements, /free, the model
pages, plugins and blog posts. Drop the three old illustration SVGs and
HomeArtifactIllustration.
The generator now writes air-gap-diorama.svg instead of a TS string,
and the component renders it inline through the existing SVGR loader,
dropping dangerouslySetInnerHTML. Step hooks move from classes to
data-dio attributes, because SVGR's SVGO prefixes class names inside
the SVG; home.css targets the attributes and the wrapper sizes the
SVG. The optimised markup is about 25% smaller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the dark fluted-glass footer with one set on a scene of a
castle in the clouds: the download pitch on open ground where the page
fades into the sky, and the wordmark, tagline, contact button and link
columns on a light card over the hill. The footer now carries the call
to action, so the homepage's separate HomeCta card is removed.

- add sectionSpacing, one vertical rhythm for the homepage's ruled
  sections, so every gap between sections is equal and the footer's
  CTA starts on the same beat
- give HomeDownloadButton a dark tone for light ground
- move the footer and socials from fixed hex colours to palette tokens
…l every page

- Cap each top-level section at --home-max (80rem) instead of the page
  shell, so content lines up on every route while the hero and footer
  images run full width (data-bleed opts a section out)
- Bring every page onto the same px-6 md:px-10 gutters: uncap
  MarketingSection and the connector hero, put the MCP pages on the site
  design, fix doubled gutters on contact and the connector tables, and
  start long-form columns on the gutter
- Centre the FAQ heading like the features section, with a flush option
  on SiteFaqItem for lists that sit inside the gutters
- Mount Lenis once in the site shell for every marketing route, settling
  it after each navigation so momentum never carries across pages

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…20px

Above 1920px the root font size grows by 4px for every 640px of width,
up to 125% at 2560px. Almost every size on the page is in rem, so the
whole page grows together. Screens 1920px wide or less don't change.

Also centre the air-gap diorama below the lg breakpoint and give the
footer card a bit more vertical padding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every frame of a failed document or analysis script repeated the run
folder's absolute path, about 130 characters each, so the 500-character
reason the agent reads kept few of the traceback's lines. The folder is
deleted once the run ends, so the child now strips it and frames read
File "script.py", line N.

Removes the matching Known gap from docs/architecture/studio.md.
Rules ran to the 80rem column edge while text sat a gutter in, so
dividers, table rules and FAQ rows overshot the text and lists read
as wider or narrower than their neighbours. Move the gutter outside
the column instead: each section is the column minus a gutter on
each side, so its edge is the text edge and every line lands on it.

- size `.ss-home-page` sections to `100% - 2 * --home-gutter`; drop
  the comparison table's first/last-column side padding
- remove `px-6 md:px-10` from section heads, text blocks, plain row
  lists and table wrappers on every marketing page, and the
  `-mx-6 md:-mx-10` pull-outs around connector tables
- keep inner padding only on boxed cells (bento, pillars, plans,
  feature tabs, card grids)
- drop the gutter wrapper from `MarketingSection` and the `flush`
  prop from `SiteFaqItem`, which every row now matches
- drop the duplicate top rule under FAQ and plugin headings
- overlay a tiled SVG film grain on the hero scene, kept out of the
  WebP so it stays sharp when the site scales up
- replace the footer scene with a 2:1 summer sky (2 MB -> 150 KB) and
  re-encode the hero scene (1.1 MB -> 290 KB)
- enlarge the trusted-by logos, larger again from md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the flat hairline grid of format cells with rounded cards, each
holding a small preview of what that format produces. The bigger formats
(podcast, slides, mind map) get large cards on a sky-blue background, and
the five written formats share one card of file tiles. Every card lifts
on hover.

Shorten the format descriptions, rename Summary/Document to
Markdown/Word, and drop the FAQ's top border so it doesn't double up
with the formats section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Centre the "For confidential work" badge and heading like the Features
section, and set the paragraph in a white rounded banner with a dark
"Learn more" pill. A cut-out dandelion hill fills the bottom-right
corner; it overflows the card so the card clips the image's cropped
left edge.

Move the section into its own folder and drop the dividers above it and
above the formats section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The desktop app runs local models with a bundled llama.cpp and downloads
them from huggingface.co only. The homepage, the /free model pages and
the privacy page still said Ollama ships in the box and that downloads
go to registry.ollama.ai.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ense column

Centre the badge and headline like the other sections and set the table
in a rounded card, with the SurfSense column raised as a white column.
Every cell gets a yes/no/partial mark, judged the same way for every
product. On phones a NotebookLM / AnythingLLM / Open Notebook switcher
compares SurfSense against one product at a time; the full table stays
in the markup for search.

Also shorten two cells and drop the dividers above and below the
section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each object had its own shadow length, the laptop was shadowed as a
solid block as tall as its screen, and grey blobs sat in front of the
rack and laptop, opposite the cast shadows. Shadows now come from one
undrawn light, half as long as each object is tall, projected from each
object's real geometry and faded as one layer so overlaps don't darken.
Contact shadows under each footprint replace the offset blobs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The three cells read as steps, not features: index on your disk, your
model or key, sources become deliverables. Offline answering now says
it needs a local model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Draw the database at 96 sides, curve each disc's drive-bay slot beside
its status light, and run both cables under the laptop base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MODSetter and others added 20 commits October 16, 2026 03:05
# Conflicts:
#	docs/architecture/documents.md
Docling read every .docx: a 4.6 s torch/transformers import on the first
Office file of a worker session, 5 s on a large contract even with the
style cache, padded pipe tables, and list numbers that matched Word on
only 31% of numbered paragraphs in the reference files (it restarts each
list, ignores start overrides and inherited numbering, and writes letters
and roman numerals as 1.1.1).

A new reader works on the package's XML directly: body paragraphs and
tables in order, Docling's heading rules, list numbers rendered as Word
shows them (1,876 of 1,876 numbered paragraphs agree with Word's own
ListString), compact tables, links, tracked changes as accepted, text
boxes once, footnotes and endnotes. The large contract reads in 0.13 s
instead of 5.1 s, and a session's first Word file in 0.05 s instead of
4.4 s. Over 791 real DOCX files the text recall against Docling is 1.0
at the median and the 5th percentile. In a clause-question A/B (20
questions, 3 models) it scored 45/60 strict against Docling's 31/60.

Docling still reads a file the reader cannot open, one with native
charts, and one with no text. Figures come from the figures-only pass,
queued at ingest for a file that embeds images.
SURFSENSE_LOCAL_DOCX_READER=docling restores Docling.
A page's text layer has line breaks but no blank lines, so the chunker cut
long pages at arbitrary lines: on a two-column paper a table's caption and
header landed in one passage and its rows in the next. A line now starts a
paragraph when the gap above it is more than 1.3 times the wider of its
neighbouring line spacings (from pdfium's character origins), captions go
with the nearer block, and a lone heading line goes with the block after
it. Only newlines are added; the text is otherwise unchanged.

Answer evidence in the top 5 passages: Boston budgets 41 -> 44 of 50, a
second corpus 20 -> 22 of 23. Answer gates against Docling's markdown:
Boston 0.70 vs 0.60 (Gemma 3 4B and Qwen3.5 9B), second corpus 0.83 vs
0.78 with table cells 28/30 vs 27/30 (three models). Gemma lost three
Boston table cells, where short paragraphs now pack across a page break.
About 0.3 ms a page.
# Conflicts:
#	surfsense_local/backend/modules/documents/tasks.py
#	surfsense_local/backend/worker/ingestion/parsing.py
Huey's consumer backs off to polling every 10 s once its queue has been
idle for a while. In the packaged app, a worker idle for a minute took
9.6 s to start an upload that its text layer then made ready in about a
second. The ingest consumer now polls at least every 0.5 s and the Studio
and plugins consumers every second; a poll is one query on huey.db, which
is its own file.
…or a deleted source

- An asked-for figures pass ran at interactive priority (100), so a Studio
  draft over many text-read sources queued one pass per source ahead of a
  note written after it. Asked passes now run at PRIORITY_ASKED (30):
  behind notes and uploads, ahead of a folder import.
- A source deleted while its figures pass read it kept a folder on Windows
  (the pass held the original open), and SQLite gave the next upload the
  same id and that folder. The pass now writes only when the same row is
  still ready with the same original, removes a folder no row owns, and an
  upload empties its document folder before the file moves in.
…t give way

- A PDF whose text layer is refused on some pages (a photo cover, a scanned
  page, a turned table page) is no longer read whole by Docling: runs of
  flagged pages are read with page_range and spliced, in page order, with
  the text-layer pages. Most-flagged files, filled forms and unreadable
  files are still read whole. Education and Capital Planning (12 pages,
  photo covers) went from 13.2 s and 28.6 s to 3.5 s and 3.4 s.
- A Docling reading longer than one batch reads 8-page batches, checks for
  cancel between them, and after 60 s gives way to a file or note queued at
  its own priority or higher, as recordings do; it resumes from a
  checkpoint stamped with the file's SHA-256, the Docling version and the
  parser pack. Batched output equals one conversion on a 20-page scan and
  on Education (markdown, figures.json and PNG bytes). A note written 10 s
  into a 20-page scan was ready in 61 s instead of 81 s.
- The figures-only pass gives way the same way between page runs and
  resumes; an idle pass gives way to any upload, an asked one only to work
  at its priority or above. The idle pass is queued for text-read PDFs of
  at most 40 pages with image objects.
# Conflicts:
#	docs/architecture/documents.md
#	surfsense_local/backend/modules/documents/tasks.py
#	surfsense_local/backend/worker/ingestion/figures/backfill.py
perf(local): sources usable in seconds: PDF text layer, Word reader without Docling, FP32 embedder on x86-64
The API runs on uvloop on macOS and Linux, whose create_connection has
no happy_eyeballs_delay, so every tunnel failed there and opencode never
reached a plugin. The option is now passed only on asyncio's own loop,
which the API runs on Windows, where it was added. Any failed connect is
answered 502 rather than closed unanswered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(local): tunnels through SurfSense's proxy work under uvloop
The 2.2.2 entry for the website's changelog: sources ready in seconds
(#2289), speech recognition for recordings and voice input (#2264, #2273),
plugin search, permissions, licensing and speed (#2256, #2260, #2261,
#2262, #2270, #2291), ChatGPT plan sign-in (#2259), MP3 podcasts (#2183),
and the fixes, known issues and developer notes since v2.2.1.
2.2.2 makes WAV, MP3, FLAC and Ogg Vorbis recordings sources: transcribed
on the machine and answered from with citations. RELEASE.md asks for the
format table to match dev before the release merge.
@vercel

vercel Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
surf-sense-frontend Ready Ready Preview Oct 16, 2026 9:42pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Oct 16, 2026

Copy link
Copy Markdown

Walkthrough

Warning

Review details and warnings were omitted to fit the comment limit.

Build audio.cpp, which the desktop release needs, had failed on every run
since 15 Oct:
- Windows: the runner now has Visual Studio 2026, whose vswhere gives its
  product line as "18" rather than the year, so CMake was asked for the
  generator "Visual Studio 18 18". The year now comes from the major version
  when the product line is not a year.
- Linux: the recogniser-pin step unpacked the only speech model, and Canary
  made it two. It now takes Nemotron by its id, the model the gate's tests
  are written for.
uvloop has no Windows build, so importing it at the top of the module
stopped every Windows backend run at collection. Only the uvloop test
needs it, and it now skips where uvloop is missing.
@MODSetter
MODSetter merged commit 176aed2 into main Oct 16, 2026
43 of 47 checks passed

This branch was successfully deployed

1 active deployment
Preview — a54a6693 Deployed Oct 16, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants